﻿//
// Created by livio on 2023/3/5.
//
#include "Net.h"

#include <stdexcept>
#include <string>

#ifdef WIN32

#include <tchar.h>

#pragma comment(lib, "ws2_32.lib")

struct NETWORKINIT {
    NETWORKINIT() {
        WSAData data{};
        auto err = WSAStartup(MAKEWORD(2, 2), &data);
        if (err != 0) {
            throw std::runtime_error{"WSAStartup failed with error:" + std::to_string(err)};
        }
    }

    ~NETWORKINIT() {
        auto err = WSACleanup();
        if (err != 0) {
            fprintf(stderr, "WSAStartup failed with error:%d\n", err);
        }
    }
};

static const NETWORKINIT _init;

static int stream_socketpair(ADDRINFO *addr_info, SOCKET sock[2]) {
    SOCKET listener, client, server;
    char opt = 1;

    listener = server = client = INVALID_SOCKET;
    listener = socket(addr_info->ai_family, addr_info->ai_socktype, addr_info->ai_protocol); //创建服务器socket并进行绑定监听等
    if (INVALID_SOCKET == listener)
        goto fail;

    setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, (const char *) &opt, sizeof(opt));

    if (SOCKET_ERROR == bind(listener, addr_info->ai_addr, addr_info->ai_addrlen))
        goto fail;

    if (SOCKET_ERROR == getsockname(listener, addr_info->ai_addr, (int *) &addr_info->ai_addrlen))
        goto fail;

    if (SOCKET_ERROR == listen(listener, 5))
        goto fail;

    client = socket(addr_info->ai_family, addr_info->ai_socktype, addr_info->ai_protocol); //创建客户端socket，并连接服务器

    if (INVALID_SOCKET == client)
        goto fail;

    if (SOCKET_ERROR == connect(client, addr_info->ai_addr, addr_info->ai_addrlen))
        goto fail;

    server = accept(listener, 0, 0);

    if (INVALID_SOCKET == server)
        goto fail;

    closesocket(listener);

    sock[0] = client;
    sock[1] = server;

    return 0;
    fail:
    if (INVALID_SOCKET != listener)
        closesocket(listener);
    if (INVALID_SOCKET != client)
        closesocket(client);
    return -1;
}

static int dgram_socketpair(ADDRINFO *addr_info, SOCKET sock[2]) {
    SOCKET client, server;
    ADDRINFO addr, *result = nullptr;
    LPCTSTR address;
    char opt = 1;

    server = client = INVALID_SOCKET;

    server = socket(addr_info->ai_family, addr_info->ai_socktype, addr_info->ai_protocol);
    if (INVALID_SOCKET == server)
        goto fail;

    setsockopt(server, SOL_SOCKET, SO_REUSEADDR, (const char *) &opt, sizeof(opt));

    if (SOCKET_ERROR == bind(server, addr_info->ai_addr, addr_info->ai_addrlen))
        goto fail;

    if (SOCKET_ERROR == getsockname(server, addr_info->ai_addr, (int *) &addr_info->ai_addrlen))
        goto fail;

    client = socket(addr_info->ai_family, addr_info->ai_socktype, addr_info->ai_protocol);
    if (INVALID_SOCKET == client)
        goto fail;

    memset(&addr, 0, sizeof(addr));
    addr.ai_family = addr_info->ai_family;
    addr.ai_socktype = addr_info->ai_socktype;
    addr.ai_protocol = addr_info->ai_protocol;

    if (AF_INET6 == addr.ai_family)
        address = _TEXT("0:0:0:0:0:0:0:1");
    else
        address = _TEXT("127.0.0.1");

    if (getaddrinfo(address, "0", &addr, &result))
        goto fail;

    setsockopt(client, SOL_SOCKET, SO_REUSEADDR, (const char *) &opt, sizeof(opt));
    if (SOCKET_ERROR == bind(client, result->ai_addr, result->ai_addrlen))
        goto fail;

    if (SOCKET_ERROR == getsockname(client, result->ai_addr, (int *) &result->ai_addrlen))
        goto fail;

    if (SOCKET_ERROR == connect(server, result->ai_addr, result->ai_addrlen))
        goto fail;

    if (SOCKET_ERROR == connect(client, addr_info->ai_addr, addr_info->ai_addrlen))
        goto fail;

    freeaddrinfo(result);
    sock[0] = client;
    sock[1] = server;
    return 0;

    fail:
    if (INVALID_SOCKET != client)
        closesocket(client);
    if (INVALID_SOCKET != server)
        closesocket(server);
    if (result)
        freeaddrinfo(result);
    return -1;
}

int socketpair(int family, int type, int protocol, SOCKET recv[2]) {
    LPCTSTR address;
    ADDRINFO addr_info;
    LPADDRINFO p_addrinfo{nullptr};
    int result = -1;

    memset(&addr_info, 0, sizeof(addr_info));
    addr_info.ai_family = family;
    addr_info.ai_socktype = type;
    addr_info.ai_protocol = protocol;
    if (AF_INET6 == family)
        address = _TEXT("0:0:0:0:0:0:0:1");
    else
        address = _TEXT("127.0.0.1");

    if (0 == GetAddrInfo(address, _TEXT("0"), &addr_info, &p_addrinfo)) {
        if (SOCK_STREAM == type)
            result = stream_socketpair(p_addrinfo, recv);   //use for tcp
        else if (SOCK_DGRAM == type)
            result = dgram_socketpair(p_addrinfo, recv);    //use for udp
        else {}
        freeaddrinfo(p_addrinfo);
    }
    return result;
}


#endif // !WIN32


